package com.kinyx.framework.reflect;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.kinyx.framework.annotation.assist.CanNotNull;
import com.kinyx.framework.annotation.assist.CanNull;
import com.kinyx.framework.annotation.assist.Note;
import com.kinyx.framework.annotation.assist.ReturnNotNull;
import com.kinyx.framework.annotation.assist.ReturnNull;
import com.kinyx.framework.annotation.assist.Tester;
import com.kinyx.framework.annotation.assist.Throws;
import com.kinyx.framework.enums.impl.ErrorCodeX;
import com.kinyx.framework.exception.KRuntimeException;
import com.kinyx.framework.utils.KArrayUtils;

import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;

/**
 * JavaBean工具类
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class KBeanUtils {

	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@ReturnNotNull
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester._newInstance4")
	private static <T> T newInstance(@CanNotNull final Class<T> clazz, @CanNotNull final Class<?>[] parameterTypes, @CanNotNull final Object[] initargs) {
		Constructor<T> constructor;
		try {
			constructor = clazz.getDeclaredConstructor(parameterTypes);
		} catch (final NoSuchMethodException e) {
			throw new KRuntimeException(e);
		} catch (final SecurityException e) {
			throw new KRuntimeException(e);
		}
		if (!constructor.isAccessible()) { constructor.setAccessible(true); }
		try {
			return constructor.newInstance(initargs);
		} catch (final InstantiationException e) {
			throw new KRuntimeException(e);
		} catch (final IllegalAccessException e) {// never happens
			throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
		} catch (final IllegalArgumentException e) {
			throw new KRuntimeException(e);
		} catch (final InvocationTargetException e) {
			throw new KRuntimeException(e);
		}
	}

	/**
	 * 调用有参构造方法时需要传入的参数
	 * @param <T> 参数泛型
	 */
	@AllArgsConstructor
	public static class Params<T> {
		/** 参数类型 */
		private Class<T> parameterType;
		/** 参数值 */
		private T initarg;
	}

	/**
	 * 新建实例对象
	 * @param <T> 泛型
	 * @param clazz 类型
	 * @param params 调用有参构造方法时需要传入的参数
	 * @return 实例对象
	 */
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@ReturnNotNull
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester._newInstance1")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester._newInstance2")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester.otherPackage._newInstance3")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester._newInstance4")
	public static <T> T newInstance(@CanNotNull final Class<T> clazz, @CanNull @Note("调用无参构造方法时，忽略该参数。") final Params<?>... params) {
		if (!KArrayUtils.isEmpty(params)) {// 有参数则调用有参构造方法
			final Class<?>[] parameterTypes = new Class<?>[params.length];
			final Object[] initargs = new Object[params.length];
			for (int i = 0; i < initargs.length; i++) {
				final Params<?> param = params[i];
				parameterTypes[i] = param.parameterType;
				initargs[i] = param.initarg;
			}
			return newInstance(clazz, parameterTypes, initargs);
		}
		Constructor<T> constructor;
		try {
			constructor = clazz.getDeclaredConstructor();
		} catch (final NoSuchMethodException e) {
			throw new KRuntimeException(e);
		} catch (final SecurityException e) {
			throw new KRuntimeException(e);
		}
		if (!constructor.isAccessible()) { constructor.setAccessible(true); }
		try {
			return constructor.newInstance();
		} catch (final InstantiationException e) {
			throw new KRuntimeException(e);
		} catch (final IllegalAccessException e) {// never happens
			throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
		} catch (final IllegalArgumentException e) {// never happens
			throw new KRuntimeException(e, ErrorCodeX.NeverHappens);
		} catch (final InvocationTargetException e) {
			throw new KRuntimeException(e);
		}
	}

	/**
	 * 新建实例对象
	 * @param <T> 泛型
	 * @param className 类名
	 * @param params 调用有参构造方法时需要传入的参数
	 * @return 实例对象
	 */
	@SuppressWarnings("unchecked")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Class<?>[], Object[])", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	@ReturnNotNull
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester._newInstance1")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester._newInstance2")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester.otherPackage._newInstance3")
	public static <T> T newInstance(@CanNotNull final String className, @CanNull @Note("调用无参构造方法时，忽略该参数。") final Params<?>... params) {
		Class<?> clazz;
		try {
			clazz = Class.forName(className);
		} catch (final ClassNotFoundException e) {
			throw new KRuntimeException(e);
		}
		return (T) newInstance(clazz, params);
	}

	/**
	 * 新建实例对象列表
	 * @param <T> 泛型
	 * @param listClass 类型
	 * @return 实例对象列表
	 */
	@ReturnNull("listClass == null")
	public static <T> List<T> newInstances(@CanNotNull final List<Class<? extends T>> listClass) {
		if (listClass == null) { return null; }
		if (listClass.isEmpty()) { return Collections.emptyList(); }
		final List<T> list = new ArrayList<>();
		listClass.forEach(clazz -> { list.add(newInstance(clazz)); });
		return list;
	}

	/**
	 * 复制属性
	 * @param sourceObj 源对象
	 * @param targetObj 目标对象
	 */
	@SuppressWarnings({
			"unchecked", "rawtypes"
	})
	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalArgumentException", when = "Field.get(Object)")
	//	@Throws(at = "KFieldUtils.get(Object, Field)", value = "IllegalAccessException", when = "Field.get(Object)")
	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalArgumentException", when = "Field.set(Object, Object)")
	//	@Throws(at = "KFieldUtils.set(Object, Field, Object)", value = "IllegalAccessException", when = "Field.set(Object, Object)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "NoSuchMethodException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "SecurityException", when = "Class.getDeclaredConstructor((Class...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InstantiationException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalAccessException", when = "Constructor.newInstance(Object...)")
	//	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "IllegalArgumentException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(Class, Param<?>...)", value = "InvocationTargetException", when = "Constructor.newInstance(Object...)")
	@Throws(at = "KBeanUtils.newInstance(String, Param<?>...)", value = "ClassNotFoundException", when = "Class.forName(String)")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester_copyProperties")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester_copyProperties1")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester_copyProperties2")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester_copyProperties3")
	@Tester("com.kinyx.framework.reflect.KBeanUtils_Tester_copyProperties4")
	public static void copyProperties(@CanNotNull final Object sourceObj, @CanNotNull final Object targetObj) {
		if (sourceObj instanceof Map) {
			if (targetObj instanceof Map) {// 集合转集合
				((Map) sourceObj).forEach((key, value) -> { ((Map) targetObj).put(key, value); });
			} else {// 集合转对象
				final List<Field> targetFields = KFieldUtils.getFieldList(targetObj.getClass(), true, false, false);
				((Map) sourceObj).forEach((key, value) -> {
					targetFields.forEach(fieldTarget -> {
						if (key.equals(fieldTarget.getName())) {
							KFieldUtils.set(targetObj, fieldTarget, value);
							return;
						}
					});
				});
			}
		} else {
			final List<Field> sourceFields = KFieldUtils.getFieldList(sourceObj.getClass(), true, false, false);
			if (targetObj instanceof Map) {// 对象转集合
				sourceFields.forEach(fieldSource -> {
					((Map) targetObj).put(fieldSource.getName(), KFieldUtils.get(sourceObj, fieldSource));
				});
			} else {// 对象转对象
				final List<Field> targetFields = KFieldUtils.getFieldList(targetObj.getClass(), true, false, false);
				sourceFields.forEach(fieldSource -> {
					final String fieldSourceName = fieldSource.getName();
					targetFields.forEach(fieldTarget -> {
						final String fieldTargetName = fieldTarget.getName();
						if (fieldSourceName.equals(fieldTargetName)) {
							KFieldUtils.set(targetObj, fieldTarget, KFieldUtils.get(sourceObj, fieldSource));
						}
					});
				});
			}
		}
	}

}
